import os
import sys
import subprocess
import asyncio
import tempfile
from src.ui.i18n import _
from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog,
    QMessageBox, QPlainTextEdit, QGroupBox, QCheckBox, QListWidget,
    QListWidgetItem, QLabel, QFormLayout, QComboBox, QTableWidget,
    QTableWidgetItem, QHeaderView, QTabWidget, QSpinBox, QStyle, QMenu
)
from PySide6.QtCore import QThread, Signal, Qt, QCoreApplication, QTimer, QElapsedTimer, QUrl
from PySide6.QtGui import QDesktopServices

from src.config import app_config
from src.ui.constants import SOURCE_LANGUAGES, TARGET_LANGUAGES
from src.processing.batch_composition_worker import BatchCompositionWorker
from src.translation.edge_tts import EdgeTTS
from src.utils.logger import get_logger
from src.utils.process import run_command
from src.ui.widgets.translation_options_widget import TranslationOptionsWidget
from src.ui.widgets.generation_options_widget import GenerationOptionsWidget
from src.ui.widgets.video_list_widget import VideoTableWidget
from src.ui.dialogs.log_dialog import LogDialog


class BatchCompositionTab(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.worker = None
        self.voices = []
        self.retry_queue = set()
        self.logger = get_logger(__name__)
        self.process_timer = QTimer(self)
        self.process_elapsed_timer = QElapsedTimer()
        self.audio_player = None
        self.listen_worker = None
        self._setup_ui()
        self._connect_signals()
        self._load_voices()
        self.reload_config()  # Load initial config
        # app_config.changed.connect(self.reload_config) # This is now handled by main_window
        self.retranslate_ui()

    def reload_config(self):
        self.logger.debug("Reloading config for batch tab...")
        self._populate_lang_combos()
        self.translation_options.reload_config()

    def _setup_ui(self):
        main_layout = QHBoxLayout(self)
        left_layout = QVBoxLayout()
        right_layout = QVBoxLayout()

        # --- Left Column ---
        self.list_group = QGroupBox()
        list_layout = QVBoxLayout(self.list_group)
        self.video_table_widget = VideoTableWidget()
        list_layout.addWidget(self.video_table_widget)
        list_button_layout = QHBoxLayout()
        self.add_videos_button = QPushButton()
        self.remove_video_button = QPushButton()
        self.clear_list_button = QPushButton()
        list_button_layout.addWidget(self.add_videos_button)
        list_button_layout.addWidget(self.remove_video_button)
        list_button_layout.addWidget(self.clear_list_button)
        list_layout.addLayout(list_button_layout)
        left_layout.addWidget(self.list_group, 1)

        self.log_edit = QPlainTextEdit()
        self.log_edit.setReadOnly(True)
        left_layout.addWidget(self.log_edit, 1)

        # --- Right Column ---
        self.tasks_group = QGroupBox()
        tasks_layout = QVBoxLayout(self.tasks_group)
        target_lang_layout = QFormLayout()
        self.global_target_lang_combo = QComboBox()
        self.global_target_lang_label = QLabel()
        target_lang_layout.addRow(
            self.global_target_lang_label, self.global_target_lang_combo)
        tasks_layout.addLayout(target_lang_layout)
        self.task_generate_srt_checkbox = QCheckBox()
        self.task_translate_srt_checkbox = QCheckBox()
        self.task_dub_video_checkbox = QCheckBox()
        self.task_compose_video_checkbox = QCheckBox()
        tasks_layout.addWidget(self.task_generate_srt_checkbox)
        tasks_layout.addWidget(self.task_translate_srt_checkbox)
        tasks_layout.addWidget(self.task_dub_video_checkbox)
        tasks_layout.addWidget(self.task_compose_video_checkbox)
        self.skip_existing_checkbox = QCheckBox()
        self.skip_existing_checkbox.setChecked(True)
        tasks_layout.addWidget(self.skip_existing_checkbox)
        parallel_layout = QFormLayout()
        self.parallel_tasks_combo = QComboBox()
        cpu_count = os.cpu_count() or 4
        for i in range(1, cpu_count + 1):
            self.parallel_tasks_combo.addItem(str(i), i)
        
        # Set default value
        default_value = app_config.get("processing.max_parallel_tasks", max(1, cpu_count // 2))
        default_index = self.parallel_tasks_combo.findData(default_value)
        if default_index != -1:
            self.parallel_tasks_combo.setCurrentIndex(default_index)
        
        self.parallel_tasks_label = QLabel()
        parallel_layout.addRow(self.parallel_tasks_label, self.parallel_tasks_combo)
        tasks_layout.addLayout(parallel_layout)

        # --- Auto Retry Options ---
        retry_layout = QHBoxLayout()
        self.auto_retry_checkbox = QCheckBox()
        self.auto_retry_checkbox.setChecked(True)
        self.retry_count_spinbox = QSpinBox()
        self.retry_count_spinbox.setRange(1, 10)
        self.retry_count_spinbox.setValue(3)
        self.retry_count_label = QLabel() # "times"
        retry_layout.addWidget(self.auto_retry_checkbox)
        retry_layout.addWidget(self.retry_count_spinbox)
        retry_layout.addWidget(self.retry_count_label)
        retry_layout.addStretch()
        tasks_layout.addLayout(retry_layout)

        right_layout.addWidget(self.tasks_group)

        self.options_tabs = QTabWidget()
        right_layout.addWidget(self.options_tabs, 1)

        # Generation Options
        self.gen_tab = QWidget()
        gen_layout = QVBoxLayout(self.gen_tab)
        self.generation_options = GenerationOptionsWidget()
        gen_layout.addWidget(self.generation_options)
        self.options_tabs.addTab(self.gen_tab, "")

        # Translation Options
        self.trans_tab = QWidget()
        trans_layout = QVBoxLayout(self.trans_tab)
        self.translation_options = TranslationOptionsWidget()
        trans_layout.addWidget(self.translation_options)
        self.options_tabs.addTab(self.trans_tab, "")

        # Dubbing Options
        self.dub_tab = QWidget()
        dub_layout = QFormLayout(self.dub_tab)
        self.tts_service_combo = QComboBox()
        self.tts_service_combo.addItems([EdgeTTS.get_name()])
        self.voice_combo = QComboBox()
        self.tts_service_label = QLabel()
        self.voice_label = QLabel()
        self.preserve_audio_duration_checkbox = QCheckBox()
        self.preserve_audio_duration_checkbox.setChecked(True)  # Default checked
        
        # Add listen button for voice preview
        self.listen_button = QPushButton(_("Listen"))
        self.listen_button.setEnabled(False)
        self.listen_button.clicked.connect(self.listen_to_voice)
        voice_layout = QHBoxLayout()
        voice_layout.addWidget(self.voice_combo, 1)
        voice_layout.addWidget(self.listen_button)
        
        dub_layout.addRow(self.tts_service_label, self.tts_service_combo)
        dub_layout.addRow(self.voice_label, voice_layout)
        dub_layout.addRow(self.preserve_audio_duration_checkbox)
        self.options_tabs.addTab(self.dub_tab, "")

        # Composition Options
        self.comp_tab = QWidget()
        comp_layout = QFormLayout(self.comp_tab)
        self.keep_audio_checkbox = QCheckBox()
        self.keep_audio_checkbox.setChecked(True)
        self.mix_bg_checkbox = QCheckBox()
        self.subtitle_mode_combo = QComboBox()
        self.subtitle_mode_combo.addItems(
            ["Soft Subtitles", "Hard Subtitles", "No Subtitles"])
        self.embed_whisper_srt_checkbox = QCheckBox()
        self.embed_whisper_srt_checkbox.setChecked(True)
        self.subtitle_mode_label = QLabel()
        self.extend_video_checkbox = QCheckBox()
        self.extend_video_checkbox.setChecked(True)  # Default checked
        comp_layout.addRow(self.mix_bg_checkbox)
        comp_layout.addRow(self.keep_audio_checkbox)
        comp_layout.addRow(self.subtitle_mode_label, self.subtitle_mode_combo)
        comp_layout.addRow(self.embed_whisper_srt_checkbox)
        comp_layout.addRow(self.extend_video_checkbox)
        self.options_tabs.addTab(self.comp_tab, "")

        bottom_layout = QHBoxLayout()
        self.time_label = QLabel()
        bottom_layout.addWidget(self.time_label)
        bottom_layout.addStretch()
        self.process_button = QPushButton()
        self.stop_button = QPushButton()
        bottom_layout.addWidget(self.process_button)
        bottom_layout.addWidget(self.stop_button)
        right_layout.addLayout(bottom_layout)

        # --- Main Layout ---
        main_layout.addLayout(left_layout, 1)
        main_layout.addLayout(right_layout, 0)

    def _connect_signals(self):
        self.add_videos_button.clicked.connect(self.add_videos)
        self.remove_video_button.clicked.connect(self.remove_selected_video)
        self.clear_list_button.clicked.connect(self.clear_video_list)
        self.process_button.clicked.connect(self.handle_start_button_click)
        self.stop_button.clicked.connect(self.stop_batch_processing)
        self.process_timer.timeout.connect(self._update_process_time)
        self.video_table_widget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.video_table_widget.customContextMenuRequested.connect(self.show_context_menu)
        self.video_table_widget.itemDoubleClicked.connect(self.on_video_double_clicked)

        self.task_generate_srt_checkbox.toggled.connect(
            lambda c: self.options_tabs.setTabEnabled(0, c))
        self.task_translate_srt_checkbox.toggled.connect(
            lambda c: self.options_tabs.setTabEnabled(1, c))
        self.task_dub_video_checkbox.toggled.connect(
            lambda c: self.options_tabs.setTabEnabled(2, c))
        self.task_compose_video_checkbox.toggled.connect(
            lambda c: self.options_tabs.setTabEnabled(3, c))

        self.auto_retry_checkbox.toggled.connect(self.retry_count_spinbox.setEnabled)

        self.global_target_lang_combo.currentIndexChanged.connect(
            self._on_global_language_changed)

        self.task_generate_srt_checkbox.setChecked(True)
        self.task_translate_srt_checkbox.setChecked(True)
        self.task_dub_video_checkbox.setChecked(True)
        self.task_compose_video_checkbox.setChecked(True)

    def retranslate_ui(self):
        self.list_group.setTitle(_("Video Queue"))
        # The VideoTableWidget now handles its own headers and retranslation
        # self.video_table_widget.setHorizontalHeaderLabels(
        #     [_("#"), _("File Name"), _("Status"), _("Elapsed Time")])
        self.video_table_widget.setup_columns() # This will re-apply translated headers
        self.add_videos_button.setText(_("Add Videos..."))
        self.remove_video_button.setText(_("Remove Selected"))
        self.clear_list_button.setText(_("Clear List"))
        self.tasks_group.setTitle(_("Tasks to Perform"))
        self.global_target_lang_label.setText(_("Target Language:"))
        self.task_generate_srt_checkbox.setText(
            _("Generate Subtitles (Whisper)"))
        self.task_translate_srt_checkbox.setText(_("Translate Subtitles"))
        self.task_dub_video_checkbox.setText(_("Generate Dubbing (EdgeTTS)"))
        self.task_compose_video_checkbox.setText(_("Compose Final Video"))
        self.skip_existing_checkbox.setText(_("Skip existing output files"))
        self.parallel_tasks_label.setText(_("Max Parallel Tasks:"))
        self.auto_retry_checkbox.setText(_("Auto-retry failed tasks:"))
        self.retry_count_label.setText(_("times"))
        self.parallel_tasks_combo.currentIndexChanged.connect(self.on_parallel_tasks_changed)
        self.options_tabs.setTabText(0, _("Subtitle Generation"))
        self.options_tabs.setTabText(1, _("Subtitle Translation"))
        self.options_tabs.setTabText(2, _("Dubbing"))
        self.tts_service_label.setText(_("TTS Service:"))
        self.voice_label.setText(_("Select Voice:"))
        self.listen_button.setText(_("Listen"))
        self.preserve_audio_duration_checkbox.setText(_("Preserve natural audio duration (don't speed up)"))
        self.preserve_audio_duration_checkbox.setToolTip(_("When checked, the generated dubbing audio will not be sped up to match the original subtitle timing. This is required for the 'Extend video for long dubs' feature to work correctly."))
        self.options_tabs.setTabText(3, _("Video Composition"))
        self.keep_audio_checkbox.setText(
            _("Keep original audio track as a secondary track"))
        self.mix_bg_checkbox.setText(
            _("Attempt to mix background sound with dubbing (Experimental)"))
        self.mix_bg_checkbox.setToolTip(
            _("Tries to remove vocals from the original audio and mix the background sound with the dubbing into the default audio track.\n"
              "This feature uses the demucs library for processing, which may significantly increase the processing time.\n"
              "Effectiveness varies depending on the video's audio source."))
        self.subtitle_mode_combo.setItemText(0, _("Soft Subtitles"))
        self.subtitle_mode_combo.setItemText(1, _("Hard Subtitles"))
        self.subtitle_mode_combo.setItemText(2, _("No Subtitles"))
        self.embed_whisper_srt_checkbox.setText(
            _("Embed Whisper SRT as secondary subtitle"))
        self.extend_video_checkbox.setText(_("Extend video for long dubs"))
        self.extend_video_checkbox.setToolTip(
            _("When the dubbing duration is longer than the subtitle duration, extend the last frame of the video to match the dubbing duration.\nThis will re-encode the extended video segments, which may take longer.\nIf unchecked, the video and audio will be out of sync."))
        self.subtitle_mode_label.setText(_("Subtitle Mode:"))
        self.time_label.setText(_("Total Time: --:--:--"))
        self.process_button.setText(_("Start Batch Processing"))
        self.stop_button.setText(_("Stop"))
        self.stop_button.setEnabled(False)

        # Repopulate language-dependent combos
        self._populate_lang_combos()
        self.generation_options.retranslate_ui()


    def stop_batch_processing(self):
        if self.worker and self.worker.isRunning():
            self.log_edit.appendPlainText(f"\n>> {_('User requested to stop the task.')}")
            self.worker.stop()
            self.stop_button.setEnabled(False)
            self.stop_button.setText(_("Stopping..."))

    def handle_start_button_click(self):
        """Handler for the 'Start Batch Processing' button click signal."""
        self.start_batch_processing()

    def start_batch_processing(self, video_paths=None):
        if self.worker and self.worker.isRunning():
            # This should not happen if buttons are managed correctly, but as a safeguard:
            QMessageBox.warning(self, _("Warning"), _("A batch process is already running."))
            return

        if video_paths is None:
            self.retry_queue.clear()  # Clear retry queue on a fresh manual start
            video_paths = self.video_table_widget.get_all_file_paths()

        # Filter out any None or empty strings that might have gotten in.
        video_paths = [path for path in video_paths if path]

        if not video_paths:
            QMessageBox.warning(self, _("Warning"), _("Please add video files first."))
            return

        tasks = {
            "generate_srt": self.task_generate_srt_checkbox.isChecked(),
            "translate_srt": self.task_translate_srt_checkbox.isChecked(),
            "dub_video": self.task_dub_video_checkbox.isChecked(),
            "compose_video": self.task_compose_video_checkbox.isChecked(),
        }

        options = self._get_current_options()
        if not options:
            return

        self.log_edit.clear()
        self.process_button.setEnabled(False)
        self.stop_button.setEnabled(True)
        self.stop_button.setText(_("Stop"))
        self.process_elapsed_timer.start()
        self.process_timer.start(1000)

        self.worker = BatchCompositionWorker(video_paths, tasks, options)
        self.worker.log_message.connect(self._handle_worker_log)
        self.worker.current_file_progress.connect(
            lambda c, t, f: self.log_edit.appendPlainText(f"\n--- [{c}/{t}] {f} ---"))
        self.worker.video_status_changed.connect(self._update_video_status)
        self.worker.video_time_updated.connect(self._update_video_time)
        self.worker.finished.connect(self.on_batch_finished)
        self.worker.error.connect(self.on_batch_error)
        self.worker.start()

    def on_batch_finished(self, success, total_time):
        from src.utils.formatter import format_seconds
        self.process_timer.stop()
        self.process_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        self.time_label.setText(
            _("Total Time: {}").format(format_seconds(total_time)))
        if success:
            # Only show the "finished" popup if there are no pending retries.
            if not self.retry_queue:
                QMessageBox.information(
                    self, _("Complete"), _("Batch tasks finished."))
        
        self.worker = None

        # Check for and start any queued retries
        if self.retry_queue:
            self.log_edit.appendPlainText(f"\n--- {_('Starting queued retry tasks...')} ---")
            retry_paths = list(self.retry_queue)
            self.retry_queue.clear()
            self.start_batch_processing(video_paths=retry_paths)

    def on_batch_error(self, error_message):
        self.process_timer.stop()
        self.log_edit.appendPlainText(
            f"\n❌ " + _("Error: {}").format(error_message))
        self.process_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        # A bit of a hack, but avoids showing a critical error on user cancellation
        if _("Task cancelled") not in error_message:
            QMessageBox.critical(self, _("Failed"), error_message)
        self.worker = None

    def add_videos(self):
        last_dir = app_config.get(
            "paths.last_video_dir", os.path.expanduser("~"))
        paths, _filter = QFileDialog.getOpenFileNames(
            self, _("Select Videos"), last_dir, _("Video Files (*.mp4 *.mkv *.mov *.avi *.flv)"))
        if paths:
            self.video_table_widget.add_videos(paths)
            app_config.set("paths.last_video_dir",
                           os.path.dirname(paths[0]), emit_signal=False)
            app_config.save(emit_signal=False)

    def remove_selected_video(self):
        selected_rows = self.video_table_widget.get_selected_rows()
        self.video_table_widget.remove_rows(selected_rows)

    def clear_video_list(self):
        self.video_table_widget.clear_list()

    def _update_video_status(self, video_path, status_text, status_key=None):
        """
        Updates the status of a video in the table.
        :param video_path: The path of the video to update.
        :param status_text: The translated text to display for the status.
        :param status_key: A non-translated, stable key for the status (e.g., "COMPLETED").
        """
        is_error = status_key in ["FAILED", "CANCELLED"]
        for i in range(self.video_table_widget.rowCount()):
            if self.video_table_widget.get_file_path(i) == video_path:
                self.video_table_widget.update_status(i, status_text, is_error=is_error)
                # Store the stable status key in UserRole for logic checks
                status_item = self.video_table_widget.item(i, 2)
                if status_item:
                    status_item.setData(Qt.UserRole, status_key)
                break
    
    def _update_video_time(self, video_path, elapsed_seconds):
        for i in range(self.video_table_widget.rowCount()):
            if self.video_table_widget.get_file_path(i) == video_path:
                self.video_table_widget.update_time(i, elapsed_seconds)
                break
    
    def _handle_worker_log(self, video_path, message, level):
        """
        Handles the unified log signal from the worker.
        This now ONLY handles displaying high-level INFO logs in the main UI console.
        Detailed logs are read from file on demand.
        """
        # Display only INFO logs in the main log console to avoid clutter
        if level == "INFO":
            # "global" is a special key for messages not tied to a specific video
            if video_path == "global":
                self.log_edit.appendPlainText(message)
            else:
                # Add video name prefix for context in the main log
                video_name = os.path.splitext(os.path.basename(video_path))[0]
                self.log_edit.appendPlainText(f"[{video_name}] {message}")

    def show_log_dialog(self):
        selected_rows = self.video_table_widget.get_selected_rows()
        if not selected_rows:
            return
        
        # Show log for the first selected item
        row = selected_rows[0]
        video_path = self.video_table_widget.get_file_path(row)
        if not video_path:
            return
            
        video_name = os.path.splitext(os.path.basename(video_path))[0]
        work_dir = os.path.join(app_config.get('paths.workspace', 'trans'), video_name)
        log_file_path = os.path.join(work_dir, f"{video_name}.log")

        log_text = ""
        if os.path.exists(log_file_path):
            try:
                with open(log_file_path, 'r', encoding='utf-8') as f:
                    log_text = f.read()
            except Exception as e:
                log_text = _("Error reading log file: {}").format(e)
        else:
            log_text = _("Log file not found at: {}").format(log_file_path)

        dialog = LogDialog(self, os.path.basename(video_path), log_text)
        dialog.exec()

    def _update_process_time(self):
        from src.utils.formatter import format_seconds
        if self.process_elapsed_timer.isValid():
            self.time_label.setText(_("Total Time: {}").format(
                format_seconds(self.process_elapsed_timer.elapsed() / 1000)))

    def _populate_lang_combos(self):
        # Save current selection
        current_global_target = self.global_target_lang_combo.currentData()

        self.global_target_lang_combo.clear()

        for code, name in TARGET_LANGUAGES.items():
            display = f"{_(name)} ({code})"
            self.global_target_lang_combo.addItem(display, code)

        # Restore selection or set default
        target_code = current_global_target or app_config.get(
            "translation.default_target_lang", "zh-CN")

        global_target_index = self.global_target_lang_combo.findData(
            target_code)
        if global_target_index != -1:
            self.global_target_lang_combo.setCurrentIndex(global_target_index)

    def _on_global_language_changed(self):
        lang_code = self.global_target_lang_combo.currentData()
        if not lang_code:
            return

        self.translation_options.set_target_language(lang_code)

        self.voice_combo.clear()
        self.voice_combo.setEnabled(False)
        self.listen_button.setEnabled(False)
        if not self.voices:
            self.voice_combo.addItem(_("Loading..."))
            return

        matching_voices = [
            v for v in self.voices if v['Locale'].startswith(lang_code)]
        if not matching_voices:
            self.voice_combo.addItem(_("No voices for {}").format(lang_code))
        else:
            for voice in sorted(matching_voices, key=lambda v: v['ShortName']):
                self.voice_combo.addItem(
                    f"{voice['ShortName']} ({voice['Gender']})", voice['Name'])
            self.voice_combo.setEnabled(True)
            self.listen_button.setEnabled(True)

    def _load_voices(self):
        self.voice_combo.addItem(_("Loading..."))

        class VoiceLoader(QThread):
            finished = Signal(list)

            def run(self):
                try:
                    loop = asyncio.get_running_loop()
                except RuntimeError:
                    loop = asyncio.new_event_loop()
                    asyncio.set_event_loop(loop)
                voices = loop.run_until_complete(EdgeTTS.get_voices())
                self.finished.emit(voices)

        self.voice_loader = VoiceLoader()
        self.voice_loader.finished.connect(self.on_voices_loaded)
        self.voice_loader.start()

    def on_voices_loaded(self, voices):
        self.voices = voices
        self._on_global_language_changed()
        # Enable listen button if there are voices and a language is selected
        lang_code = self.global_target_lang_combo.currentData()
        if self.voices and lang_code:
            matching_voices = [
                v for v in self.voices if v['Locale'].startswith(lang_code)]
            if matching_voices:
                self.listen_button.setEnabled(True)

    def on_parallel_tasks_changed(self, index):
        new_limit = self.parallel_tasks_combo.itemData(index)
        if new_limit:
            # Save to config so it persists
            app_config.set("processing.max_parallel_tasks", new_limit)
            # If a worker is running, update it in real-time
            if self.worker and self.worker.isRunning():
                self.worker.update_max_workers(new_limit)
                self.log_edit.appendPlainText(f">> {_('Max parallel tasks updated to:')} {new_limit}")

    def show_context_menu(self, pos):
        selected_rows = self.video_table_widget.get_selected_rows()
        if not selected_rows:
            return

        row = self.video_table_widget.rowAt(pos.y())
        if row < 0:
            return
            
        video_path = self.video_table_widget.get_file_path(row)
        if not video_path:
            return

        menu = QMenu()
        
        # Get status from UserRole for robust logic
        status_item = self.video_table_widget.item(row, 2)
        status_key = status_item.data(Qt.UserRole) if status_item else None
        status_text = status_item.text() if status_item else ""
        is_error = status_key in ["FAILED", "CANCELLED"]

        # --- Menu Actions ---
        if is_error:
            retry_action = menu.addAction(_("Retry Task"))
            retry_action.triggered.connect(self.retry_single_task)

        if status_key not in [None, "PENDING", "QUEUED"]: # None is for newly added files
            log_action = menu.addAction(_("View Log"))
            log_action.triggered.connect(self.show_log_dialog)
        
        menu.addSeparator()
        
        play_action = menu.addAction(_("Play Video"))
        play_action.triggered.connect(self.play_selected_video)

        # Find and add "Play Dubbed Video" options by scanning the filesystem
        dubbed_videos = self._find_dubbed_videos(video_path)
        if dubbed_videos:
            if len(dubbed_videos) == 1:
                lang, path = dubbed_videos[0]
                play_dubbed_action = menu.addAction(_("Play Dubbed Video"))
                play_dubbed_action.triggered.connect(lambda: self._play_video(path))
            else:
                dub_menu = menu.addMenu(_("Play Dubbed Video"))
                for lang, path in dubbed_videos:
                    lang_name = TARGET_LANGUAGES.get(lang, lang)
                    action = dub_menu.addAction(f"{lang_name} ({lang})")
                    # The lambda must accept the 'checked' argument from the triggered signal
                    action.triggered.connect(lambda checked=False, p=path: self._play_video(p))

        open_dir_action = menu.addAction(_("Open File Location"))
        open_dir_action.triggered.connect(self.open_file_location)

        open_work_dir_action = menu.addAction(_("Open Project Directory"))
        open_work_dir_action.triggered.connect(self.open_project_directory)
        
        menu.addSeparator()
        
        remove_action = menu.addAction(_("Remove from List"))
        remove_action.triggered.connect(self.remove_selected_video)

        menu.exec(self.video_table_widget.mapToGlobal(pos))

    def _play_video(self, path):
        if path and os.path.exists(path):
            QDesktopServices.openUrl(QUrl.fromLocalFile(path))
        else:
            QMessageBox.warning(self, _("File Not Found"),
                                _("The video file could not be found at the location:\n{}").format(path))

    def play_selected_video(self):
        selected_rows = self.video_table_widget.get_selected_rows()
        if not selected_rows: return
        video_path = self.video_table_widget.get_file_path(selected_rows[0])
        self._play_video(video_path)

    def _find_dubbed_videos(self, source_video_path):
        """Scans the project directory for dubbed videos and returns them."""
        if not source_video_path:
            return []

        video_name = os.path.splitext(os.path.basename(source_video_path))[0]
        workspace_dir = app_config.get('paths.workspace', 'trans')
        project_dir = os.path.join(workspace_dir, video_name)

        if not os.path.isdir(project_dir):
            return []

        found_videos = []
        for filename in os.listdir(project_dir):
            # Match pattern: {video_name}.{lang_code}.mp4
            if filename.startswith(f"{video_name}.") and filename.endswith(".mp4"):
                parts = filename.removesuffix(".mp4").split('.')
                if len(parts) > 1:
                    lang_code = parts[-1]
                    # Check if the lang_code is a valid target language
                    if lang_code in TARGET_LANGUAGES:
                        full_path = os.path.join(project_dir, filename)
                        found_videos.append((lang_code, full_path))
        
        return sorted(found_videos)

    def open_file_location(self):
        selected_rows = self.video_table_widget.get_selected_rows()
        if not selected_rows: return
        video_path = self.video_table_widget.get_file_path(selected_rows[0])
        if video_path and os.path.exists(video_path):
            self._select_file_in_explorer(video_path)

    def _select_file_in_explorer(self, path):
        """Selects a file in the default file explorer."""
        if sys.platform == "win32":
            try:
                subprocess.run(['explorer', '/select,', os.path.normpath(path)])
            except FileNotFoundError:
                # Fallback if explorer is not found
                QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.dirname(path)))
        elif sys.platform == "darwin":
            try:
                subprocess.run(['open', '-R', path])
            except FileNotFoundError:
                # Fallback for unusual macOS setups
                QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.dirname(path)))
        else:
            # Fallback for Linux and other OSes
            QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.dirname(path)))

    def open_project_directory(self):
        selected_rows = self.video_table_widget.get_selected_rows()
        if not selected_rows: return
        video_path = self.video_table_widget.get_file_path(selected_rows[0])
        if video_path:
            video_name = os.path.splitext(os.path.basename(video_path))[0]
            work_dir = os.path.join(app_config.get('paths.workspace', 'trans'), video_name)
            if not os.path.exists(work_dir):
                os.makedirs(work_dir)
            QDesktopServices.openUrl(QUrl.fromLocalFile(work_dir))

    def retry_single_task(self):
        selected_rows = self.video_table_widget.get_selected_rows()
        if not selected_rows:
            return
        
        row = selected_rows[0]
        video_path = self.video_table_widget.get_file_path(row)
        if not video_path:
            return

        if self.worker and self.worker.isRunning():
            self.retry_queue.add(video_path)
            self.video_table_widget.update_status(row, _("Queued for Retry"), is_error=False)
            self.log_edit.appendPlainText(f"[{os.path.basename(video_path)}] {_('Task has been added to the retry queue.')}")
            return

        # If no worker is running, proceed with immediate retry for this single task.
        self.log_edit.appendPlainText(f"\n--- {_('Retrying task for:')} {os.path.basename(video_path)} ---")

        # Reset status for this row
        self.video_table_widget.update_status(row, _("Queued..."), is_error=False)

        # Get current settings
        tasks = {
            "generate_srt": self.task_generate_srt_checkbox.isChecked(),
            "translate_srt": self.task_translate_srt_checkbox.isChecked(),
            "dub_video": self.task_dub_video_checkbox.isChecked(),
            "compose_video": self.task_compose_video_checkbox.isChecked(),
        }
        options = self._get_current_options()
        if not options: return # _get_current_options shows a warning if config is bad

        # The log file will be created automatically by the new task logger

        # Run a new worker for a single video
        self.worker = BatchCompositionWorker([video_path], tasks, options)
        self.worker.log_message.connect(self._handle_worker_log)
        self.worker.current_file_progress.connect(
            lambda c, t, f: self.log_edit.appendPlainText(f"\n--- [{c}/{t}] {f} ---"))
        self.worker.video_status_changed.connect(self._update_video_status)
        self.worker.video_time_updated.connect(self._update_video_time)
        # Use a lambda to distinguish single task finish from batch finish
        self.worker.finished.connect(lambda s, t: self.on_single_task_finished(video_path, s, t))
        self.worker.error.connect(self.on_batch_error)
        self.worker.start()

        # Update UI
        self.process_button.setEnabled(False)
        self.stop_button.setEnabled(True)

    def on_single_task_finished(self, video_path, success, total_time):
        from src.utils.formatter import format_seconds
        self.log_edit.appendPlainText(
            _("Retry task for {} finished in {}.").format(os.path.basename(video_path), format_seconds(total_time))
        )
        self.worker = None
        self.process_button.setEnabled(True)
        self.stop_button.setEnabled(False)

    def on_video_double_clicked(self, item):
        """Plays the video when its table item is double-clicked."""
        if not item:
            return
        # The file path is stored in the item in the second column (index 1)
        file_item = self.video_table_widget.item(item.row(), 1)
        if file_item:
            video_path = file_item.data(Qt.UserRole)
            if video_path and os.path.exists(video_path):
                QDesktopServices.openUrl(QUrl.fromLocalFile(video_path))

    def _get_current_options(self):
        translation_opts = self.translation_options.get_options()
        if self.task_translate_srt_checkbox.isChecked() and translation_opts['translator'] == 'OpenAI' and not translation_opts.get('model_config'):
            QMessageBox.warning(self, _("Warning"), _(
                "Please select a translation model in the 'Subtitle Translation' tab."))
            return None

        generation_opts = self.generation_options.get_options()
        if self.task_generate_srt_checkbox.isChecked():
            if not generation_opts.get("whisper_model"):
                QMessageBox.warning(self, _("Warning"), _(
                    "Please select a Whisper model in the 'Subtitle Generation' tab."))
                return None

        return {
            **generation_opts,
            **translation_opts,
            "tts_service": self.tts_service_combo.currentText(),
            "voice": self.voice_combo.currentData(),
            "preserve_audio_duration": self.preserve_audio_duration_checkbox.isChecked(),
            "keep_original_audio": self.keep_audio_checkbox.isChecked(),
            "remove_vocals": self.mix_bg_checkbox.isChecked(),
            "subtitle_mode": self.subtitle_mode_combo.currentText(),
            "embed_whisper_srt": self.embed_whisper_srt_checkbox.isChecked(),
            "extend_video_for_dub": self.extend_video_checkbox.isChecked(),
            "skip_existing": self.skip_existing_checkbox.isChecked(),
            "max_parallel_tasks": self.parallel_tasks_combo.currentData(),
            "auto_retry": self.auto_retry_checkbox.isChecked(),
            "retry_count": self.retry_count_spinbox.value(),
        }

    def listen_to_voice(self):
        """Listen to a preview of the selected voice."""
        if self.voice_combo.count() == 0 or not self.voice_combo.isEnabled():
            return

        selected_voice_data = self.voice_combo.currentData()
        if not selected_voice_data:
            return
            
        # Extract the voice name from the data (similar to dubbing tab)
        selected_voice = selected_voice_data.get("Name") if isinstance(selected_voice_data, dict) else selected_voice_data
        
        current_locale = ""
        # Try to determine the locale from the global target language
        lang_code = self.global_target_lang_combo.currentData()
        if lang_code:
            # Find a voice that matches this locale
            for voice in self.voices:
                if voice['Locale'].startswith(lang_code):
                    current_locale = voice['Locale']
                    break
        
        # Fallback to checking the selected voice data directly
        if not current_locale and isinstance(selected_voice_data, dict):
            current_locale = selected_voice_data.get("Locale", "")
        
        if 'en' in current_locale.lower():
            sample_text = _("Hello, welcome to this application.")
        else:
            sample_text = _("Voice, giving new soul to video.")

        self.listen_button.setEnabled(False)
        self.listen_button.setText(_("Generating..."))

        class ListenWorker(QThread):
            finished = Signal(str, str)  # error_message, file_path

            def __init__(self, parent=None):
                super().__init__(parent)
                self.logger = get_logger(__name__)

            def run(self):
                # Safely run asyncio code within a QThread
                try:
                    loop = asyncio.get_running_loop()
                except RuntimeError:  # 'RuntimeError: There is no current event loop...'
                    loop = asyncio.new_event_loop()
                    asyncio.set_event_loop(loop)

                try:
                    tts_service = EdgeTTS()
                    # Create temporary files with correct extensions
                    with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as mp3_tmpfile:
                        mp3_tmp_path = mp3_tmpfile.name
                    
                    with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as wav_tmpfile:
                        wav_tmp_path = wav_tmpfile.name

                    # Generate the audio with Edge TTS (outputs MP3)
                    loop.run_until_complete(tts_service.save(
                        sample_text, mp3_tmp_path, selected_voice))
                    
                    # Check if the MP3 file was created and has content
                    if not os.path.exists(mp3_tmp_path) or os.path.getsize(mp3_tmp_path) == 0:
                        raise ValueError("No audio was received. Please verify that your parameters are correct.")

                    # Use ffmpeg to convert mp3 to wav for simpleaudio
                    command = [
                        "ffmpeg", "-i", mp3_tmp_path,
                        "-acodec", "pcm_s16le",  # PCM signed 16-bit little-endian
                        "-ac", "1",             # Mono
                        "-ar", "16000",         # 16kHz sample rate
                        "-y", wav_tmp_path
                    ]
                    run_command(command, log_callback=self.logger.debug)
                    
                    # Check if the WAV file was created and has content
                    if not os.path.exists(wav_tmp_path) or os.path.getsize(wav_tmp_path) == 0:
                        raise ValueError("Failed to convert audio to WAV format.")

                    # Clean up the temporary mp3 file
                    os.remove(mp3_tmp_path)

                    self.finished.emit(None, wav_tmp_path)
                except Exception as e:
                    # Clean up any temporary files that might have been created
                    try:
                        if 'mp3_tmp_path' in locals() and os.path.exists(mp3_tmp_path):
                            os.remove(mp3_tmp_path)
                        if 'wav_tmp_path' in locals() and os.path.exists(wav_tmp_path):
                            os.remove(wav_tmp_path)
                    except:
                        pass
                    self.finished.emit(str(e), None)

        self.listen_worker = ListenWorker()
        self.listen_worker.finished.connect(self.on_listen_finished)
        self.listen_worker.start()

    def on_listen_finished(self, error, file_path):
        """Handle the completion of the voice preview generation."""
        self.listen_button.setEnabled(True)
        self.listen_button.setText(_("Listen"))

        if error:
            QMessageBox.warning(self, _("Listen Failed"), _(
                "Failed to generate listen audio: {}").format(error))
            if file_path and os.path.exists(file_path):
                os.remove(file_path)
            return

        if not file_path:
            return

        # Play audio using system audio player (non-blocking)
        try:
            if os.path.exists(file_path):
                # Use system default audio player in a non-blocking way
                if os.name == 'nt':  # Windows
                    os.startfile(file_path)
                elif sys.platform == "darwin":  # macOS
                    subprocess.Popen(["afplay", file_path])
                else:  # Linux
                    subprocess.Popen(["aplay", file_path])
                
                # Schedule cleanup after a delay (assuming audio playback is short)
                from PySide6.QtCore import QTimer
                QTimer.singleShot(5000, lambda: self._cleanup_audio_file(file_path))
        except Exception as e:
            self.logger.error(f"Failed to play audio: {e}")
            QMessageBox.warning(self, _("Listen Failed"), _(
                "Failed to play audio: {}").format(str(e)))
            
    def _cleanup_audio_file(self, file_path):
        """Clean up the temporary audio file after playback."""
        try:
            if os.path.exists(file_path):
                os.remove(file_path)
        except Exception as e:
            self.logger.error(f"Failed to remove temp audio file: {e}")

    class AudioPlayer(QThread):
        def __init__(self, file_path, logger, parent=None):
            super().__init__(parent)
            self.file_path = file_path
            self.logger = logger

        def run(self):
            try:
                # Use system audio player instead of simpleaudio
                import subprocess
                if os.path.exists(self.file_path):
                    # Use system default audio player
                    if os.name == 'nt':  # Windows
                        os.startfile(self.file_path)
                    elif sys.platform == "darwin":  # macOS
                        subprocess.run(["afplay", self.file_path], check=True)
                    else:  # Linux
                        subprocess.run(["aplay", self.file_path], check=True)
            except Exception as e:
                self.logger.error(f"Failed to play audio: {e}")
            finally:
                try:
                    if os.path.exists(self.file_path):
                        os.remove(self.file_path)
                except OSError as e:
                    self.logger.error(
                        f"Failed to remove temp audio file: {e}")

